/*
 * LoadPNG.cpp
 *
 *  Created on: Jul 12, 2009
 *      Author: kurt
 */

#include "Image.h"
#include "ImageUtil.h"
#include <png.h>
#include <csetjmp>
#include <valloc.h>
#include <psputils.h>
#include <cstdlib>
#include <malloc.h>
#include <cstring>
using namespace std;
using namespace objdraw;

#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))

void ImageUtil_PNG_ReadFn(png_structp png_ptr, png_bytep data, png_size_t length)
{
	VirtualFile * f = (VirtualFile *)png_get_io_ptr(png_ptr);
	f->read(data,length,1);
}

bool ImageUtil::is_png(string file)
{
	VirtualFile * fp = new VirtualFile(file.c_str(),"rb");
	if(fp == NULL) 
		return false;
	bool ret = is_png(fp);
	fp->close();
	fp->Delete();
	return ret;
}
#define PNG_BYTES_TO_CHECK 8
bool ImageUtil::is_png(VirtualFile * fp)
{
	if(fp == NULL) return false;
	
	unsigned char buf[PNG_BYTES_TO_CHECK];
	if(fp->read(buf,1,PNG_BYTES_TO_CHECK) != PNG_BYTES_TO_CHECK)
		return false;
//	rewind back to original position
	fp->seek(-PNG_BYTES_TO_CHECK,SEEK_CUR);
	return png_check_sig(buf,PNG_BYTES_TO_CHECK);
}

Image * ImageUtil::loadImageFilePNG(string file, PixelFormat pixelFormat , bool swizzle, bool in_vram, DrawingCanvas * canvas)
{
	VirtualFile * fp = new VirtualFile(file.c_str(),"rb");
	Image * img = loadImageFilePNG(fp,pixelFormat,swizzle,in_vram,canvas);
	if(fp != NULL)
	{
		fp->close();
		fp->Delete();
	}
	return img;
}
Image * ImageUtil::loadImageFilePNG(VirtualFile * fp, PixelFormat pixelFormat , bool swizzle, bool in_vram, DrawingCanvas * canvas)
{
	if(fp == NULL || !is_png(fp)) 
		return NULL;
	
	Image * rimg = new Image(canvas);
	rimg->image = (Image::ImageData *) malloc(sizeof(Image::ImageData));
	if(rimg->image == NULL)
	{
		rimg->Delete();
		return NULL;
	}
	Image::ImageData * image = rimg->image;
	memset(image,0,sizeof(Image::ImageData));
	image->pixelFormat = pixelFormat;
	image->in_vram = in_vram;
	
	png_structp png_ptr;
	png_infop info_ptr;
	
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
	if(png_ptr == NULL)
	{
		rimg->Delete();
		return NULL;
	}
	info_ptr = png_create_info_struct(png_ptr);
	if(info_ptr == NULL)
	{
		rimg->Delete();
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		return NULL;
	}
	if (setjmp(png_jmpbuf(png_ptr)))
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		rimg->Delete();
		return NULL;
	}
	
	png_set_read_fn(png_ptr,fp,(png_rw_ptr)ImageUtil_PNG_ReadFn);
//	png_init_io(png_ptr, fp);
//	png_set_sig_bytes(png_ptr, 8);
	
	if(pixelWidths[pixelFormat] <= 8)
	{
		png_read_png(png_ptr,info_ptr,
			PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_BGR, NULL );
	}
	else
	{
		png_read_png( png_ptr, info_ptr, 
			PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_BGR, NULL );
	}
	
	png_uint_32 width = info_ptr->width;
	png_uint_32 height = info_ptr->height;
	png_uint_32 depth = info_ptr->bit_depth;
	int color_type = info_ptr->color_type;
	
	png_byte **pRowTable = info_ptr->row_pointers;
	unsigned char r=0, g=0, b=0, a=0;
	int wantedPixelFormat = pixelFormat;
	if (!info_ptr->num_palette && pixelWidths[pixelFormat] <= 8)
	{
		pixelFormat = GU_PSM_8888;
		image->pixelFormat = pixelFormat;
		image->in_vram = false;
	}
	else
	{
		image->pixelFormat = pixelFormat;
		image->in_vram = in_vram;
	}
	image->width = width;
	image->height = height;
	image->swizzled = false;
	
	if(image->width > 512)
		image->textureWidth = image->width;
	else
		image->textureWidth = getNextPower2(image->width);
	
	if(image->height > 512)
		image->textureHeight = image->height;
	else
		image->textureHeight = getNextPower2(image->height);
	
	image->size = (image->textureWidth * image->textureHeight * pixelWidths[image->pixelFormat]) >> 3;
	if(image->in_vram)
		image->data = (uint *)valloc(image->size);
	else
		image->data = (uint *)memalign(16,image->size);
	
	if(image->data == NULL)
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		rimg->Delete();
		return NULL;
	}
	
	if(pixelWidths[pixelFormat] <= 8)
	{
		image->palette = Image::CreatePalette(MIN(info_ptr->num_palette, 1 << paletteSizes[pixelFormat]), in_vram, GU_PSM_8888);
		if(image->palette == NULL)
		{
			png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
			rimg->Delete();
			return NULL;
		}
		info_ptr->num_palette = MIN(info_ptr->num_palette, image->palette->nentries);
		for(int i = 0; i < info_ptr->num_palette; i++)
		{
			int r = info_ptr->palette[i].red;
			int g = info_ptr->palette[i].green;
			int b = info_ptr->palette[i].blue;
			int a = 0xff;
		}
		sceKernelDcacheWritebackInvalidateRange(image->palette->data,image->palette->size);
	}
	
	u32 *p_dest4 = (u32*)image->data;
	u16 *p_dest2 = (u16*)image->data;
	u8 *p_dest1 = (u8*)image->data;
	u32 x, y;
	int color_per_entry = 8 / depth;
	int color_offset, pixel_value = 0;
	int mask = (1 << depth) - 1;
	
	for ( y = 0; y < height; ++y )
	{
		const png_byte * pRow = pRowTable[y];

		for ( x = 0; x < width; ++x )
		{
			switch ( color_type )
			{
				case PNG_COLOR_TYPE_GRAY:
					r = g = b = *pRow++;
					if ( r == 0 && g == 0 && b == 0 )
						a = 0x00;
					else
						a = 0xff;
					break;
				case PNG_COLOR_TYPE_GRAY_ALPHA:
					r = g = b = *pRow++;
					if ( r == 0 && g == 0 && b == 0 )
						a = 0x00;
					else
						a = 0xff;
					pRow++;
					break;
				case PNG_COLOR_TYPE_RGB:
					b = *pRow++;
					g = *pRow++;
					r = *pRow++;
					a = 0xff;
					break;
				case PNG_COLOR_TYPE_RGB_ALPHA:
					b = *pRow++;
					g = *pRow++;
					r = *pRow++;
					a = *pRow++;
					break;
			
				case PNG_COLOR_TYPE_PALETTE:
					color_offset = x % color_per_entry;
					pixel_value = (*pRow >> (8 - depth * (color_offset + 1))) & mask;
					
					if (x % color_per_entry == color_per_entry - 1)
						pRow++;
					if (image->palette)
					{
						r = (((u32*)image->palette->data)[pixel_value]) & 0xff;
						g = ((((u32*)image->palette->data)[pixel_value]) >> 8) & 0xff;
						b = ((((u32*)image->palette->data)[pixel_value]) >> 16) & 0xff;
						a = ((((u32*)image->palette->data)[pixel_value]) >> 24) & 0xff;
					}
					else	
					{
						b = info_ptr->palette[pixel_value].blue;
						g = info_ptr->palette[pixel_value].green;
						r = info_ptr->palette[pixel_value].red;
						a = 0xff;
					}
					break;
			}
			if (pixelFormat == GU_PSM_8888)
				p_dest4[x] = RGBA(r,g,b,a);
			else if (pixelFormat == GU_PSM_5650)
				p_dest2[x] = RGB16(r,g,b);
			else if (pixelFormat == GU_PSM_5551)
				p_dest2[x] = RGBA15(r,g,b,a);
			else if (pixelFormat == GU_PSM_4444)
				p_dest2[x] = RGBA12(r,g,b,a);
			else if (pixelFormat == GU_PSM_T8)
				p_dest1[x] = pixel_value;
			else if (pixelFormat == GU_PSM_T4)				
			{
				p_dest1[x >> 1] &= ~(15 << ((x & 1) << 2));
				p_dest1[x >> 1] |= (pixel_value & 15) << ((x & 1) << 2);
			}
		}

		p_dest1 += ( image->textureWidth * pixelWidths[pixelFormat]) >> 3;
		p_dest2 += ( image->textureWidth );
		p_dest4 += ( image->textureWidth );
	}
	
	sceKernelDcacheWritebackInvalidateRange(image->data,image->size);
	rimg->setStart(Location(0,0));
	rimg->setEnd(Location(image->width,image->height));
//	TODO swizzle images and convert pixel formats
	if(wantedPixelFormat != pixelFormat)
		rimg = ImageUtil::convertImageTo(rimg,in_vram,wantedPixelFormat);
	if(swizzle)
		rimg->swizzle();
	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
	return rimg;
}
